package baidu_ai_sr;

import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.json.JSONException;
import org.json.JSONObject;

import com.baidu.aip.http.AipHttpClient;
import com.baidu.aip.http.AipRequest;
import com.baidu.aip.http.AipResponse;
import com.baidu.aip.http.EBodyFormat;
import com.baidu.aip.http.Headers;
import com.baidu.aip.http.HttpContentType;
import com.baidu.aip.speech.AipSpeech;
import com.baidu.aip.speech.TtsResponse;
import com.baidu.aip.util.AipClientConst;
import com.baidu.aip.util.Base64Util;
import com.baidu.aip.util.SignUtil;
import com.baidu.aip.util.Util;

import cn.rmshadows.joffice.JofficeToolKit;
import cn.rmshadows.joffice.PrimaryController;
import func_controller.BaiduSpeechRecognizeController;
import joffice_vad.Jvad;

public class BaiduSpeechRecognize {
//	private final static boolean METHOD_RAW = false; // 默认以json方式上传音频文件
//	// 填写网页上申请的appkey 如 $apiKey="g8eBUMSokVB1BHGmgxxxxxx"
//	private final static String APP_KEY = "kVcnfD9iW2XVZSMaLMrtLYIz";
//	// 填写网页上申请的APP SECRET 如 $SECRET_KEY="94dc99566550d87f8fa8ece112xxxxx"
//	private final static String SECRET_KEY = "O9o1O213UgG5LFn0bDGNtoRN3VWl2du6";
//	// 单声道
//	private final static int CHANNEL = 1;
//	private static String URL = "https://vop.baidu.com/server_api"; // 可以改为https
//
//	// 1537 表示识别普通话，使用输入法模型。 其它语种参见文档
//	private static int DEV_PID;
//	private static String SCOPE = "audio_voice_assistant_get";
	// private int LM_ID;//测试自训练平台需要打开此注释

	// 用户唯一标识，用来区分用户，计算UV值。建议填写能区分用户的机器 MAC 地址或 IMEI 码，长度为60字符以内。
	private static String CUID = "cn.rmshadows.joffice.baidu_asr_app";
	// 采样率固定值
	private final static int RATE = 16000;
	private static LinkedHashMap<String, String> conf_para = JofficeToolKit
			.loadConf(BaiduSpeechRecognizeController.bdConf.getAbsolutePath(), "=");
	private static HttpsAipSpeech client = new HttpsAipSpeech(conf_para.get("APP_ID"), conf_para.get("APP_KEY"),
			conf_para.get("SECRET_KEY"));

	/**
	 * 返回识别结果
	 * 
	 * @param res json对象
	 * @return String 识别结果字符串
	 */
	static String getResult(JSONObject res) {
		String re = "";
		try {
			re = res.get("result").toString().replace("[\"", "").replace("\"]", "");
		} catch (Exception e) {
			e.printStackTrace();
		}
		return re;
	}

	/**
	 * 获取json错误 两种错误：SDK AIP
	 * 
	 * @param res json对象
	 * @return String[] 错误信息和错误编号
	 */
	static String[] getError(JSONObject res) {
		String[] err = new String[2];
		err[0] = res.get("err_msg").toString().replace("[\"", "").replace("\"]", "");
		if (res.has("err_no")) {
			err[1] = res.get("err_no").toString().replace("[\"", "").replace("\"]", "");
		} else if (res.has("error_code")) { // SDK错误
			err[1] = res.get("error_code").toString().replace("[\"", "").replace("\"]", "");
		} else {
			err[1] = "No_no_NO_code";
		}
		return err;
	}

	/**
	 * 将所给的音频文件转化位百度语音识别所需要的pcm文件
	 * 
	 * @param filepath 源文件
	 * @return String 转换后的pcm文件路径
	 */
	public static String convert2bdPCM(String filepath) {
		try {
			// 如果是PCM文件，不做处理
			if (getFormat(filepath).equals("pcm")) {
				return filepath;
			}
			// 如果不是wav，转换为wav
			if (!getFormat(filepath).equals("wav")) {
				filepath = JofficeToolKit.convertTo16k1cAudioFile(filepath, 1).getAbsolutePath();
			}
			return JofficeToolKit.convertTo16k1cAudioFile(filepath, 0).getAbsolutePath();
		} catch (Exception e) {
//			e.printStackTrace();
		}
		return "NoExistedFile";
	}

	/**
	 * 识别拖拽文件的方法
	 * 
	 * @param drag_files 文件列表
	 * @param opt        选项
	 * @throws InterruptedException
	 */
	public static void baidu_drag_asr(List<File> drag_files, HashMap<String, Object> opt) throws InterruptedException {
		for (File drag : drag_files) {
			// 停止识别
			if (!BaiduSpeechRecognizeController.is_bding || PrimaryController.fv_change) {
				BaiduSpeechRecognizeController.bd_bt_status(1, false);
				return;
			}
			// 转为PCM
			File f = new File(BaiduSpeechRecognize.convert2bdPCM(drag.getAbsolutePath()));
			// 如果文件读取出错,开始下个循环
			if (!f.exists()) {
				BaiduSpeechRecognizeController.bd_bt_status(1, false);
				LinkedList<String> tp_err = new LinkedList<String>();
				tp_err.add("文件获取出错。");
				BaiduSpeechRecognizeController.bdSetText(f.getAbsolutePath() + "\n", true);
				for (String r : tp_err) {
					BaiduSpeechRecognizeController.bdSetText(r, true);
				}
				BaiduSpeechRecognizeController.bdSetText("\n", true);
				continue;
			}
			// 百度语音识别文件输入列表
			LinkedList<File> input_files = new LinkedList<File>();
			// 如果PCM文件大于一分钟 文件大小大约是1920k
			if (f.length() >= 1919900) {
				// 开始Jvad
				Jvad.Jvad_File(f.getAbsolutePath());
				for (File i : JofficeToolKit.listJvadFile()) {
					input_files.add(i);
//					System.out.println(i.getName());
				}
			} else {
				input_files.add(f);
			}
			// 启用一个新得线程识别
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					List<String> bd_re = baidu_asr_files(input_files, opt, false, false, 1);
					BaiduSpeechRecognizeController.bdSetText(f.getAbsolutePath() + "\n", true);
					for (String r : bd_re) {
						BaiduSpeechRecognizeController.bdSetText(r, true);
					}
					BaiduSpeechRecognizeController.bdSetText("\n", true);
				}
			});
			System.out.println(t.getName());
			t.start();
			// 等待线程完成
			t.join();
			BaiduSpeechRecognizeController.bdSetText("\n\n", true);
		}
		BaiduSpeechRecognizeController.bd_bt_status(1, false);
	}

	/**
	 * 百度语音识别 文件列表 百度语音识别要求: 60 秒以下pcm,采样率 16000、8000，16 bit 位深，单声道
	 * 
	 * @param input_files PCM文件列表
	 * @param options     选项 添加到Json请求
	 * @param viewRate    是否显示进度
	 * @param setBTN      是否设置按钮状态
	 * @param BTN_index         要设置的按钮索引 0:麦克风按钮 1:文件识别按钮
	 * @return 识别结果
	 */
	public static List<String> baidu_asr_files(LinkedList<File> input_files, HashMap<String, Object> opt,
			boolean viewRate, boolean setBTN, int BTN_index) {
//		BaiduSpeechRecognizeController.bd_bt_status(1, true);
		System.out.println(conf_para);
		List<String> bd_re = Collections.synchronizedList(new LinkedList<String>());
//		LinkedList<String> bd_re = new LinkedList<String>();
		for (int i = 0; i < input_files.size(); i++) {
			bd_re.add("");
		}
		int done = 0;
		int all_to_do = input_files.size();
		for (File i : input_files) {
			// 停止识别
			if (!BaiduSpeechRecognizeController.is_bding || PrimaryController.fv_change) {
				BaiduSpeechRecognizeController.bd_bt_status(BTN_index, false);
				return bd_re;
			}
			LinkedList<String[]> r = baidu_asr_single_file(i.getAbsolutePath(), opt);
			done += 1;
			// 识别进度
			if (viewRate && done < all_to_do) {
				BaiduSpeechRecognizeController.setRate(done, all_to_do);
			}
			// 如果识别成功，列表只有一项
			if (r.size() == 1) {
				bd_re.set(input_files.indexOf(i), r.get(0)[0]);
			} // 识别失败，列表有两项
			else {
				String errors = "";
				for (String[] s : r) {
					errors += s[0] + "&Code/NO: " + s[1] + ";";
				}
				bd_re.set(input_files.indexOf(i), "==>>" + i.getAbsolutePath() + "识别失败:" + errors + "<<==");
			}
		}
		if (setBTN) {
			BaiduSpeechRecognizeController.bd_bt_status(BTN_index, false);
		}
		return bd_re;
	}

	/**
	 * 百度语音识别 单个PCM文件 百度语音识别要求: 60 秒以下pcm、wav、amr、m4a,采样率 16000、8000，16 bit 位深，单声道
	 * 最基本的方法 如果失败，尝试一次补执行
	 * 
	 * @param file_path 文件路径
	 * @param options   选项 添加到Json请求
	 * @return 识别结果。成功：返回一个字符串数组，失败发挥两个数组。
	 */
	private static LinkedList<String[]> baidu_asr_single_file(String file_path, HashMap<String, Object> options) {
		LinkedList<String[]> re_lst = new LinkedList<String[]>();
		// 识别结果
		String[] api_re = new String[] { "" };
		// 其他默认添加的参数在这里
		options.put("cuid", CUID);
		try {
//			LinkedHashMap<String, String> conf_para = JofficeToolKit
//					.loadConf(BaiduSpeechRecognizeController.bdConf.getAbsolutePath(), "=");
			// 改成自己的类，重写https覆盖罢了
//			HttpsAipSpeech client = new HttpsAipSpeech(conf_para.get("APP_ID"), conf_para.get("APP_KEY"),
//					conf_para.get("SECRET_KEY"));
			// 可选：设置网络连接参数
			client.setConnectionTimeoutInMillis(2000);
			client.setSocketTimeoutInMillis(60000);
			// 可选：设置代理服务器地址, http和socket二选一，或者均不设置
//			client.setHttpProxy("proxy_host", proxy_port); // 设置http代理
//			client.setSocketProxy("proxy_host", proxy_port); // 设置socket代理
			// 可选：设置log4j日志输出格式，若不设置，则使用默认配置
			// 也可以直接通过jvm启动参数设置此环境变量
//			System.setProperty("aip.log4j.conf", null);
			// 调用接口 e.g: client.asr(null, api_re, RATE, options)
			// 是否成功识别
			boolean is_succeed = false;
			// 两次尝试
			for (int i = 0; i < 2; i++) {
				JSONObject res = client.asr(file_path, getFormat(file_path), RATE, options);
				System.out.println(file_path + " 的第" + (i + 1) + "次尝试：\n" + res.toString(2));
				String[] status_code = getError(res);
				// 如果识别成功直接返回
				if (status_code[0].equals("success.")) {
					api_re[0] = getResult(res);
					is_succeed = true;
					break;
				} else {
					re_lst.add(status_code);
				}
			}
			if (is_succeed) {
				re_lst.clear();
				re_lst.add(api_re);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return re_lst;
	}

	/**
	 * 返回文件的扩展名
	 * 
	 * @param file_name 完整文件名
	 * @return 扩展名
	 */
	private static String getFormat(String file_name) {
		try {
			// 文件格式, 支持pcm/wav/amr 格式，极速版额外支持m4a 格式
			String format = file_name.substring(file_name.length() - 3);
			return format;
		} catch (Exception e) {
//			e.printStackTrace();
		}
		return null;
	}

}

/**
 * 重写了ASR和TTS的URL地址为HTTPS罢了
 * 
 * @author ryan
 * @description: 重写了ASR和TTS的URL地址为HTTPS罢了
 */
class HttpsAipSpeech extends AipSpeech {

	public HttpsAipSpeech(String appId, String apiKey, String secretKey) {
		super(appId, apiKey, secretKey);
	}

	@Override
	public JSONObject asr(byte[] data, String format, int rate, HashMap<String, Object> options) {
		AipRequest request = new AipRequest();

		preOperation(request); // get access token
		if (this.isBceKey.get()) {
			// get access token failed!
			return Util.getGeneralError(AipClientConst.OPENAPI_NO_ACCESS_ERROR_CODE,
					AipClientConst.OPENAPI_NO_ACCESS_ERROR_MSG);
		}
		// state.setState(EAuthState.STATE_TRUE_AIP_USER);
		String base64Content = Base64Util.encode(data);
		request.addBody("speech", base64Content);
		request.addBody("format", format);
		request.addBody("rate", rate);
		request.addBody("channel", 1);
		String cuid = SignUtil.md5(accessToken, "UTF-8");
		request.addBody("cuid", cuid);
		request.addBody("token", accessToken);
		request.addBody("len", data.length);
		if (options != null) {
			request.addBody(options);
		}
		// no post operation

		request.setUri("https://vop.baidu.com/server_api");
		request.setBodyFormat(EBodyFormat.RAW_JSON);
		request.addHeader(Headers.CONTENT_TYPE, HttpContentType.JSON_DATA);

		return requestServer(request);

	}

	@Override
	public TtsResponse synthesis(String text, String lang, int ctp, HashMap<String, Object> options) {
		AipRequest request = new AipRequest();
		preOperation(request);
		if (this.isBceKey.get()) {
			// get access token failed!
			TtsResponse response = new TtsResponse();
			JSONObject msg = Util.getGeneralError(AipClientConst.OPENAPI_NO_ACCESS_ERROR_CODE,
					AipClientConst.OPENAPI_NO_ACCESS_ERROR_MSG);
			response.setResult(msg);
			return response;
		}
		request.addBody("tex", text);
		request.addBody("lan", lang);
		request.addBody("tok", accessToken);
		request.addBody("ctp", ctp);
		String cuid = SignUtil.md5(accessToken, "UTF-8");
		request.addBody("cuid", cuid);
		if (options != null) {
			request.addBody(options);
		}
		request.setUri("https://tsn.baidu.com/text2audio");

		TtsResponse response = new TtsResponse();
		AipResponse res = AipHttpClient.post(request);
		if (res == null) {
			response.setResult(Util.getGeneralError(-1, "null response from server"));
			return response;
		}
		Map<String, List<String>> header = res.getHeader();
		if (header.containsKey("content-type")) {
			String contentType = res.getHeader().get("content-type").get(0);
			if (contentType.contains("json")) {
				String data = res.getBodyStr();
				JSONObject json = new JSONObject(data);
				response.setResult(json);
			} else {
				byte[] binData = res.getBody();
				response.setData(binData);
			}
		} else {
//            LOGGER.error("synthesis get no content-type in header: " + header);
//            LOGGER.info("synthesis response status: " + res.getStatus());
			try {
				JSONObject json = new JSONObject(res.getBodyStr());
				response.setResult(json);
			} catch (JSONException e) {
				response.setData(res.getBody());
			}
		}
		return response;
	}

}
